package com.twu.biblioteca;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.twu.biblioteca.domain.Book;
import com.twu.biblioteca.domain.Movie;
import com.twu.biblioteca.domain.LibraryItem;
import com.twu.biblioteca.domain.User;

import static java.lang.Math.max;
import static java.util.Arrays.fill;

public class BibliotecaApp {

    private final LibraryItemManager<Book> bookManager;
    private final LibraryItemManager<Movie> movieManager;

    private final BufferedReader reader;
    private final PrintStream os;

    private User authenticatedUser = null;
    private final List<User> users;

    public BibliotecaApp(BufferedReader reader, PrintStream os, Book[] books, Movie[] movies, User[] users) {
        this.reader = reader;
        this.os = os;

        bookManager = new LibraryItemManager<>(Arrays.asList(books));
        movieManager = new LibraryItemManager<>(Arrays.asList(movies));

        this.users = new ArrayList<>(Arrays.asList(users));
    }

    public static void main(String[] args) throws IOException {
        Book[] defaultBooks = {
            new Book("Kent Beck", 2003, "Test-driven development: by example"),
            new Book("Steve Krug", 2014, "Don't Make Me Think, Revisited: A Common Sense Approach to Web Usability")
        };

        Movie[] defaultMovies = {
                new Movie("Pulp Fiction", 1994, "Quentin Tarantino", null),
                new Movie("Harry Potter and the Deathly Hallows - Part 2", 2011, "David Yates", 9)
        };

        User[] defaultUsers = {
                new User("111-1111", "Matt Newman", "password", "mnewman@thoughtworks.com", "0412032293")
        };

        BibliotecaApp bibliotecaApp = new BibliotecaApp(
                new BufferedReader(new InputStreamReader(System.in)),
                System.out,
                defaultBooks,
                defaultMovies,
                defaultUsers
        );

        bibliotecaApp.run();
    }

    public static List<String> getPaddedBookTableRows(List<Book> books) {

        if (books.size() == 0)
            return new ArrayList<>();

        String template = getTemplate(books);

        List<String> result = new ArrayList<>();

        for (Book book : books){
            result.add(String.format(template, book.author, book.year, book.title));
        }

        return result;
    }

    /**
     * Generates a template suitable for use in String.format(), with correct padding
     * to print all allBooks in @param("allBooks") in a columns.
     *
     * @param books the list of all allBooks to print in the table
     * @return the template (e.g. "%-10s %-4s %-20s")
     */
    private static String getTemplate(List<Book> books) {
        int maxAuthorLength = -1;
        int maxTitleLength = -1;

        // get max author & title lengths for padding
        for (Book book : books) {
            maxAuthorLength = max(book.author.length(), maxAuthorLength);
            maxTitleLength = max(book.title.length(), maxTitleLength);
        }

        return "%-" + maxAuthorLength + "s %-4s %-" + maxTitleLength + "s";
    }

    public void run() throws IOException {
        printWelcome();

        String input = reader.readLine();

        // Main input loop
        while( input != null ){

            boolean done = false;

            Command command = Command.getCommand(input);

            if (command == null) {
                os.println("Select a valid option!");
            } else {

                switch (command) {
                    case LOGIN: {
                        doLogin();
                        break;
                    }
                    case LIST_BOOKS: {
                        for (String row : getPaddedBookTableRows(bookManager.getAvailable())) {
                            os.println(row);
                        }
                        break;
                    }
                    case CHECKOUT_BOOK: {
                        doCheckoutBook();
                        break;
                    }
                    case RETURN_BOOK: {
                        doReturnBook();
                        break;
                    }
                    case LIST_MOVIES: {
                        for (Movie movie : movieManager.getAvailable()) {
                            os.println(movie.title);
                        }
                        break;
                    }
                    case CHECKOUT_MOVIES: {
                        doCheckoutMovie();
                        break;
                    }
                    case CHECK_BOOK_HOLDER: {
                        String bookTitle = reader.readLine();
                        User user = bookManager.getCheckoutInfo(bookTitle);
                        if (user != null) {
                            os.println(String.format("Book %s is currently held by %s", bookTitle, user.userID ));
                        } else {
                            os.println("Book currently not in library or not checked out");
                        }
                        break;
                    }
                    case CHECK_USER_INFORMATION: {
                        doCheckUserInformation();
                        break;
                    }
                    case QUIT: {
                        done = true;
                        break;
                    }
                }

            }

            if (done)
                break;

            os.print("> ");
            input = reader.readLine();
        }
    }

    private void doCheckUserInformation() {
        if (!checkLogin()) return;

        os.println(authenticatedUser.toFormattedString());
    }

    private void doCheckoutMovie() throws IOException {
        if (!checkLogin()) return;

        String movieTitle = reader.readLine();
        if (movieManager.checkout(movieTitle, authenticatedUser)) {
            os.println("Thank you! Enjoy the movie.");
        } else {
            os.println("That movie is not available.");
        }
    }

    private void doReturnBook() throws IOException {
        if (!checkLogin()) return;

        String bookTitle = reader.readLine();
        if (bookManager.returnItem(bookTitle, authenticatedUser)) {
            os.println("Thank you for returning the book.");
        } else {
            os.println("That is not a valid book to return.");
        }
    }

    private void doCheckoutBook() throws IOException {
        if (!checkLogin()) return;

        String bookTitle = reader.readLine();
        if (bookManager.checkout(bookTitle, authenticatedUser)) {
            os.println("Thank you! Enjoy the book.");
        } else {
            os.println("That book is not available.");
        }
    }

    private boolean checkLogin() {
        if (authenticatedUser == null) {
            os.println("You need to log in!");
            return false;
        }
        return true;
    }

    private void doLogin() throws IOException {
        os.print("Enter username: ");
        String username = reader.readLine();
        os.print("Enter password: ");
        String password = reader.readLine();

        for (User user : users) {
            if (user.userID.equals(username) && user.password.equals(password)) {
                authenticatedUser = user;
                return;
            }
        }

        os.println("Invalid username or password");
    }

    private void printWelcome() {
        os.println("Welcome to Biblioteca!");
        os.println();

        os.println("Main Menu:");
        for(Command command : Command.values()) {
            os.println("\t" + command.commandText);
        }
        os.println();

        os.print("> ");
    }

    private enum Command {
        LOGIN("Login"),
        LIST_BOOKS("List Books"),
        CHECKOUT_BOOK("Checkout Book"),
        RETURN_BOOK("Return Book"),
        LIST_MOVIES("List Movies"),
        CHECKOUT_MOVIES("Checkout Movie"),
        CHECK_BOOK_HOLDER("Check current book holder"),
        CHECK_USER_INFORMATION("Check user information"),
        QUIT("Quit")
        ;

        public final String commandText;

        private Command(final String text){
            this.commandText = text;
        }

        public static Command getCommand(final String text) {

            for (Command command : Command.values()) {
                if (command.commandText.equals(text))
                    return command;
            }

            return null;
        }
    }
}
